Services Web Delphi - John COLIBRI. |
- résumé : Principe et fonctionnement des services Web. construction d'un Serveur et de Clients de services Web.
- mots clé : Service Web - Web Services - CGI, ISAPI, Web App Debugger -
iInvokable
- logiciel utilisé : Windows XP personnel, Delphi 2006
- matériel utilisé : Pentium 2.800 Mhz, 512 Meg de mémoire, 250 Giga disque dur
- champ d'application : Delphi 6, Delphi 7, Delphi 2006, Turbo Delphi, Delphi 2007 Win32 et .Net
- niveau : développeur Delphi
- plan :
1 - Principe des Web Services
Le but fondamental d'un Service Web est de récupérer sur un poste le résultat d'un calcul effectué sur un autre poste. Voici quelques exemples simples: - consultation de la météo
- consultation et réservation de trains / avions / hôtels
- récupération de cotations boursières et calculs sur ces valeurs
Pour des documents plus lourds: - traduction de textes (BabelFish)
- téléchargement de carte en fournissant des coordonnées (longitude, lattitude) ou une adresse postale (Mappoint)
- consultation de produits Amazon
Outre le dialogue en utilisant ou non un explorateur internet, les services web
permettent aussi d'effectuer des traitement distants entre deux application ("Business to Business, ou encore B2B). Par exemple: - un libraire (un point de vente quelconque, une usine) vend (consomme) des
produits. Lorsque le stock attend un certain seuil, il faut réapprovisionner. La façon traditionnelle est de téléphoner ou d'envoyer un fax. Un peu mieux, utiliser Internet. Et, naturellement, sans aucune
intervention humaine, un Service Web. Par exemple le logiciel de déstockage contient les seuils de réapprovisionnement, et dès que celui ci est franchi, une commande est envoyée automatiquement
- tout système de surveillance (un laminoir)
2 - Construction d'un Service Web 2.1 - Fonctionnement des Web Services
Nous allons illustrer ceci sur un exemple très simple de conversion monétaire: conversion de Dollars en Euros (et réciproquement éventuellement). Pour effectuer ce calcul:
- l'utilisateur lance une application de conversion qui contient l'adresse du Service Web de conversion, tape une valeur en Dollars et clique sur un bouton
- le Service Web reçoit la demande, calcule le résultat, et renvoit la réponse
- l'utilisateur voit le résulat de la conversion
Ainsi présenté, ce type de traitement est clairement du type Client /
Serveur. Voici le déroulement du dialogue | le Serveur est lancé. Il contient
- un exécutable (.EXE ou .DLL) contenant les FUNCTIONs et des PROCEDUREs que des Clients peuvent invoquer. Dans notre cas une fonction qui convertit des Dollars en Euros:
Function f_euro(p_dollar: Integer): Integer |
- un Serveur TCP/IP capable de recevoir une demande Client, lancer l'exécutable, et renvoyer la réponse
Voici notre Serveur à l'écoute des Clients:
| |
un client lance un .EXE qui contient un Service Web Client. Il demande la conversion d'un montant en Dollar: |
| le Serveur TCP/IP reçoit la requête, et charge l'exécutable qui fait la conversion : | | the TCP/IP Server renvoie la réponse:
|
Vous aurez remarqué que ce type d'échange est très similaire aux
échanges CGI ou ISAPI, à la différence que le Client n'a pas besoin d'être un exlorateur Web, et la requête n'est pas provoquée par
un clic sur un bouton <INPUT situé dans une balise <FORM d'une page .HMTL. Le Client est contenu dans une application Windows normale (un .EXE).
2.2 - Les Composants Nécesaires
Un service Web a besoin pour fonctionner - d'un réseau TCP/IP
- d'un serveur HTTP capable de comprendre le protocole des Services Web
- d'une extension du serveur, CGI ou ISAPI, qui contient le traitement
effectué par le service
- de clients qui vont interroger le Service pour obtenir des résultats
2.3 - Le Réseau TCP/IP Celui-ci est disponible par défaut sur la plupart des OS, dont Windows XP et similaires
2.4 - Le protocole SOAP 2.4.1 - SOAP= HTTP + XML L'exécution depuis un poste d'un traitement sur un autre fait partie des techniques RPC (Remote Procedure Call). Parmi les moyens pour mettre en
oeuvre des RPC, citons - CORBA (Common Object Request Broker Architecture, qui est très complet, mais très vaste et très complexe
- DCOM (Distributed Component Object Model) qui est l'extension "distribuée" de COM. Solution purement Microsoft, et peu à peu tombé en désuétude à cause de sa difficulté de mise en oeuvre
- et finalement les Services Web utilisant le protocole SOAP (Simple Object Access Protocol
SOAP spécifie simplement comment se déroulera le dialogue entre le Serveur et
les Clients. Devant le refus de DCOM, Microsoft a sous-traité à Don BOX la création d'un nouveau protocole, et le résultat a été SOAP. SOAP se veut une simplification par rapport à DCOM. Aussi, Don BOX a choisi:
- d'utiliser le réseau TCP/IP
- le protocole HTTP
- un contenu au format .XML
Donc: - TCP/IP pour son ubiquité
- HTTP car c'est le protocole utilisé par le Web (serveurs et explorateurs
Web). Donc universellement accepté, et utilisant sur les PC le port 80, qui est pratiquement toujours laissé libre par les pare feu (sinon vous n'avez pas accès au Web, ce qui de nos jours est terrible)
- pour poser la question, une simple requête au format CGI aurait en général suffi. Mais la réponse peut être complex (une Table complète, par exemple), il fallait trouver un format adéquat. XML (eXtended Markup Language) a
été retenu.
2.4.2 - Le Format SOAP Rassurez-vous, vous n'aurez ni à construire les requêtes au format HTTP, ni a gérer ce protocole, ni à écrire ou lire du .XML. Les couches Delphi se chargeront de tout.
Néanmoins présentons rapidement le contenu des données échangées: | lorsque le Client envoie sa requête, le Client envois au Serveur un
texte composé d'une en-tête HTTP et d'un contenu avec la fonction appelée et ses paramètres au format .XML:
POST /p_server_euro.co_euro/soap/i_server_euro HTTP/1.1 SOAPAction: "urn:u_i_server_euro-i_server_euro#f_euro" Content-Type: text/xml User-Agent: Borland SOAP 1.2 Host: localhost Content-Length: 525 Connection: Keep-Alive
Cache-Control: no-cache <?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
// -- ...ooo... Ed: truncated >
<SOAP-ENV:Body SOAP-ENV:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/">
<NS1:compute_euro xmlns:NS1="urn:u_i_server_euro-i_server_euro">
<p_euro xsi:type="xsd:int">30</p_euro>
</NS1:compute_euro> </SOAP-ENV:Body>
</SOAP-ENV:Envelope> | où: - p_server_euro est le nom de notre exécutable (p_server_euro.exe)
- i_server_euro est le nom de l'INTERFACE Delphi qui définit les méthodes disponibles
- f_euro est la fonction appelée
- 30 est le montant en Dollars
|
| le Serveur retourne la réponse avec le message suivant:
HTTP/1.1 200 OK Content-Type: text/xml Content-Length: 465 Content: <?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
// -- ...ooo... Ed: truncated >
<SOAP-ENV:Body SOAP-ENV:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/">
<NS1:f_euroResponse xmlns:NS1="urn:u_i_server_euro-i_server_euro">
<return xsi:type="xsd:int">15</return>
</NS1:f_euroResponse> </SOAP-ENV:Body>
</SOAP-ENV:Envelope> | où 15 est le résultat en Euros |
Les messages comportent donc
- l'en-tête HTTP classique
- une "enveloppe" au format .XML qui contient soit la requête soit la réponse
2.5 - WSDL
Nous allons écrire un Serveur de Services Web et un Client de Services Web. Si nous souhaitions consulter les livres Amazon ou réserver un train, le
Serveur de Service Web est déjà écrit (par la SNCF). Il nous suffit de créer le Client Web Service. Il faut toutefois connaître : - le nom des méthodes offertes par le service SNCF
- le nombre et le type des paramètres de chaque méthode
Ces informations sont disponibles dans un document .WSDL (Web Service Definition Language, prononcé "ouicedelle"). Voici le .WSDL de notre requête
<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
// -- ...ooo... Ed: truncated
name="i_server_euroservice"
// -- ...ooo... Ed: truncated >
<message name="f_euro0Request">
<part name="p_euro" type="xs:int"/>
</message> <message name="f_euro0Response">
<part name="return" type="xs:int"/>
</message> <message name="compute_euro1Request">
<part name="p_euro" type="xs:int"/>
</message> <message name="compute_euro1Response"/>
// -- ...ooo... Ed: truncated |
Vous distinguerez dans ce document (tronqué: il fait environ 50 lignes) les
noms du service, de notre fonction f_euro et le paramètre p_euro et son type Int. Comme nous n'aurons pas à écrire nous-même ce type de données, nous ne nous attarderons pas sur son contenu.
Le point important est que, pour écrire un Client Web Service, nous pouvons interroger le Serveur Web Service pour lui demander quels service il offre.
Une fois ce document récupéré, nous pouvons créer le Client. Delphi va même plus loin en offrant un Wizard qui à partir du document .WSDL va générer un UNITé qui importer l'INTERFACE permettant de dialoguer avec
le Serveur. Nous utiliserons ce Wizard d'importation .WSDL ci-dessous.
2.6 - Le Serveur HTTP Il suffit que le poste serveur dispose d'un Serveur HTTP. Parmi les possibilités, citons
- sur Windows 95 et Windows 98, PWS (Personal Web Server)
- sur XP Home, aucun serveur n'est fourni par Microsoft. Nous pouvons toujours
acheter er installer IIS (Internet Information Server) ou Appache. Nous pouvons utiliser des solutions purement Delphi:
- depuis Delphi 6, le WAD (Web Application Debugger)
- divers serveurs construits à partir des composants TCP/IP Indy
- nous avons nous-mêmes utilisé un serveur HTTP CGI construit à partir de nos sockets, et adapté pour les Services Web
- pour XP Pro, nous disposons d'une version IIS light
Dans cet article, nous utiliserons les Serveur HTTP de débugging intégré à Delphi, WAD.
2.7 - Type d'exécutable Service Web
L'exécutable qui contiendra nos méthodes et qui sera lancé par le Serveur TCP/IP peut avoir trois formats: - une .DLL ISAPI (Internet Server API) ou NSAPI (NetScape API
- une extension serveur CGI (Common Gateway Interface)
- une application incluant le Serveur HTTP et une extension CGI
Les deux premières solutions nécessitent l'existence d'un Serveur HTTP (IIS,
Appache ou autre), alors que la troisième emploie le Serveur Indy de mise au point intégré à Delphi, WAD. Nous utiliserons WAD, qui est fourni avec Delphi
3 - Le Serveur Web Service 3.1 - Construction de l'extension Serveur 3.1.1 - Mise en place en utilisant le Wizard Voici les étapes pour créer notre service de conversion:
| démarrez Delphi 2006 Windows | |
sélectionnez le Wizzard qui crée une application de Service Web en sélectionnant "File | New | Other | Delphi Project | Web Services | Soap Server" | | le Wizard nous propose 3 solutions: Ces possibilités correspondent: - une .DLL ISAPI (Internet Server API) ou NSAPI (NetScape API
- une extension serveur CGI (Common Gateway Interface)
- une application incluant le Serveur HTTP et une extension CGI
Comme indiqué ci-dessus, nous utiliserons la troisième solution qui
fonctionne avec le Serveur HTTP Indy fourni avec Delphi et qui en plus permet de débugger notre Serveur | |
sélectionnez "Web App Debugger" (alias WAD) | | la boite d'édition devient accessible |
| tapez le nom d'une co-classe. Nous suggérons co_euro_dollar, puis tapez "ok" | |
Delphi nous demande si nous souhaitons créer automatiquement l'INTERFACE et l'IMPLEMENTATION de noter Service:
| | tapez "Yes" | |
Delphi nous demande le nom de notre Service | | nous suggérons d'appeler l'INTERFACE, et par conséquent vous pouvez taper
- dans "Service Name, "_server_euro_dollar" (le I sera ajouté automatiquement)
- dans "Unit Identifier" "u_i_server_euro_dollar"
Supprimez aussi "Generate Comments" pour alléger le texte
Puis cliquez "Ok" | |
Delphi génère les fichiers nécessaires au Serveur de Services Web |
Ces fichiers comprennent: - le projet (.DPR)
- l'UNITé qui contient l'INTERFACE, appelée actuellement U_I_SERVER_EURO_DOLLARINTF
- l'UNITé qui contient la CLASSe qui implémentera not INTERFACE, appelée U_I_SERVER_EURO_DOLLARIMPL
- un tWebModule permettant de réceptionner les requêtes du Client Web Service, appelé actuellement UNIT2
- une tForm, qui actuellement ne contient aucun composant, mais qui sera
affichée chaque fois que le Serveur est activé
Nous suggérons de renommer les différentes parties de la façon suivante: |
sélectionnez "Project Manager | PROJECT1.EXE | click droit | Rename" et tapez P_SERVER_EURO_DOLLAR_WAD | |
renommez U_I_SERVER_EURO_DOLLARINTF U_I_SERVER_EURO_DOLLAR | | renommez U_I_SERVER_EURO_DOLLARIMPL U_C_SERVER_EURO_DOLLAR |
| renommez UNIT2 U_WM_SERVER_EURO_DOLLAR | | renommez UNIT1 U_F_SERVER_EURO_DOLLAR |
Crée un répertoire SERVER_WAD, et sélectionnez "File | Save All"
3.1.2 - Définition des méthodes du Service Web Il faut à présent placer dans l'INTERFACE les méthodes que nous souhaitons
utiliser. Nous allons utiliser une FUNCTION pour passer du dollar à l'euro, et une PROCEDURE pour la conversion inverse. Par conséquent: |
sélectionnez "Project Manager | u_i_server_euro_dollar". | | le code généré par le wizzard contient les importations, une INTERFACE
actuellement vide, et une initialisation que enregistre notre INTERFACE | | dans le code, ajoutez la définition de nos deux méthodes:
unit u_i_server_euro_dollar; interface
uses InvokeRegistry, Types, XSBuiltIns;
type i_server_euro_dollar=
interface(IInvokable)
['{54C806EE-35DD-4F1C-8530-4B31F1DBFE3A}']
function f_dollar_to_euro(p_dollar: Double): Double; StdCall;
procedure euro_to_dollar(p_euro: Double;
var pv_dollar: Double); StdCall;
end; // i_server_euro_dollar implementation
initialization
InvRegistry.RegisterInterface(TypeInfo(i_server_euro_dollar));
end. | |
Notez que - notre INTERFACE i_server_euro_dollar descend de iInvokable. C'est ce qui
permettra de provoquer un appel depuis le Client Web Service
- les méthodes DOIVENT avoir l'attribut STDCALL pour pouvoir être invoquées
- pour pouvoir retrouver cette INTERFACE parmi d'autres qui seraient
éventuellement disponibles sur le même Serveur, Delphi a placé automatiquement un GUID (Globally Unique Identifier). Nous pouvons changer ce numéro tant que nous le souhaitons en tapant Shift-Ctrl-G. La
valeur de cet identifiant unique a pour seul but de pouvoir identifier notre INTERFACE. Ce GUID ne sera pas directement utilisé par notre code dans la suite (il ne sera pas invoqué par notre code, ou par le Client). De plus ce
GUID ne correspond pas à un objet COM. C'est uniquement un moyen d'identification unique.
- InvRegistry.RegisterInterface permet de stocker les informations qui
permettront d'appeler la CLASSe qui implémente notre INTERFACE. Notez que cet enregistrement est purement temporaire. Rien ne sera stocké à ce niveau dans la base de registres (Registry). La table de correspondance
entre le nom "i_server_euro_dollar" et l'INTERFACE i_server_euro_dollar est créée lorsque notre CGI Serveur est chargé
3.1.3 - L'implémentation de notre INTERFACE
Il nous faut à présent implémenter les méthodes que nous avons proposé dans notre INTERFACE. Par conséquent: |
copiez les deux méthodes que nous avons placées dans l'INTERFACE dans le presse-papier | | sélectionnez "Project Manager | u_c_server_euro_dollar".
| | le code généré par le wizzard contient les importations, une CLASS actuellement vide et une intialization que enregistre notre CLASSe |
| modifiez le nom de l'UNITé qui contient notre INTERFACE en U_I_SERVER_EURO_DOLLAR |
| collez les deux définitions de méthode dans la CLASSe | |
tapez Shift-Ctrl-C pour générer les implémentations de nos deux méthodes | | dans ces méthodes, tapez les calculs de conversion Le résultat est:
unit u_c_server_euro_dollar;
interface
uses InvokeRegistry, Types, XSBuiltIns
, u_i_server_euro_dollar; type c_server_euro_dollar=
class(TInvokableClass, i_server_euro_dollar)
public
function f_dollar_to_euro(p_dollar: Double): Double; StdCall;
procedure euro_to_dollar(p_euro: Double;
var pv_dollar: Double); StdCall;
end; // c_server_euro_dollar implementation
// -- c_server_euro_dollar
function c_server_euro_dollar.f_dollar_to_euro(p_dollar: Double): Double;
begin Result:= p_dollar / 1.51;
end; // f_dollar_to_euro
procedure c_server_euro_dollar.euro_to_dollar(p_euro: Double;
var pv_dollar: Double);
begin pv_dollar:= p_euro* 1.51;
end; // euro_to_dollar initialization
InvRegistry.RegisterInvokableClass(c_server_euro_dollar);
end. | |
Notez que:
- notre CLASSe doit descendre de tInvokableClass, et de notre INTERFACE i_server_euro_dollar
- l'initialisation de notre UNITé appelle
InvRegistry.RegisterInvokableClass qui stocke dans notre base de correspondance notre CLASSe
3.1.4 - Le WebModule Nous n'avons pas besoin de modifier ce module, mais nous allons le présenter
rapidement. Le code est le suivant unit u_wm_server_euro_dollar;
interface
uses SysUtils, Classes, HTTPApp, InvokeRegistry, WSDLIntf, TypInfo,
WebServExp, WSDLBind, XMLSchema, WSDLPub, SOAPPasInv, SOAPHTTPPasInv,
SOAPHTTPDisp, WebBrokerSOAP;
type TWebModule1=
class(TWebModule)
HTTPSoapDispatcher1: THTTPSoapDispatcher;
HTTPSoapPascalInvoker1: THTTPSoapPascalInvoker;
WSDLHTMLPublish1: TWSDLHTMLPublish;
procedure WebModule1DefaultHandlerAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
private public
end; // TWebModule2
var WebModule1: TWebModule1; implementation
uses WebReq; {$R *.dfm}
procedure TWebModule1.WebModule1DefaultHandlerAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
WSDLHTMLPublish1.ServiceInfo(Sender, Request, Response, Handled);
end; // WebModule1DefaultHandlerAction initialization
WebRequestHandler.WebModuleClass := TWebModule1;
end. |
et:
En effet, le WebModule contient aussi une procédure
WebModule1DefaultHandlerAction. Cette événement correspond à un ActionItem créé automatiquement par le Wizard. Nous pouvons le voir de la façon suivante:
L'action qui provoque les calculs (et pas la fourniture du fichier .WSDL) est
implicite (pas affichée dans l'éditeur d'actions).
3.1.5 - La fenêtre principale Comme nous avons créé une application WAD, elle a une fenêtre (ce ne serait pas le cas pour les ISAPI ou CGI purs).
Nous n'avons pas besoin d'y toucher. En général nous la colorions un peu pour pouvoir la visualiser plus facilement, et nous la positionnons à droite de
l'écran (création de tForm.OnCreate et initialisation de Left et Top) pour éviter que Delphi ne positionne la fenêtre de façon plutôt aléatoire.
3.2 - Compilation de notre Serveur Web
3.2.1 - Indy 9 Le Serveur HTTP intégré WAD est construit à partir du Serveur HTTP Indy. Pour Delphi 2006, il s'avère que ce serveur WAD a été compilé avec Indy 9.
Lors de l'installation de Delphi 2006, il nous a été demandé de choisir entre Indy 9 et Indy 10. Si vous avez sélectionné Indy 9, vous pouvez sauter ce paragraphe.
Si vous avez sélectionné Indy 10, il faut permettre au compilateur de retrouver les .DCU correspondant à Indy 9. Pour cela: |
sélectionnez C:\Program Files\Borland\BDS\4.0\lib\Indy9 et collez cette adresse dans le press papier |
| sélectionnez "Project | Options | Directories | Search Path" et collez-y ce chemin |
3.2.2 - Compilation du Serveur Nous compilons notre projet
Notez que:
3.3 - Création du fichier WSDL
3.3.1 - Pourquoi créer le fichier .WSDL Normalement, pour créer le Client, nous n'avons pas besoin de créer un fichier physique correspondant: nous pouvons interroger le Serveur Web Service pour
lui demander ces données. Notre version de Delphi 2006 ne permet cependant pas de récupérer ces données par une URL. En revanche l'importation depuis un fichier sur disque fonctionne.
Enregistrement de ServerInfo
Le Web App Debugger va nous présenter une liste des CGI disponibles. En fait, il s'agit d'un service Web qui nous fournit la liste de NOS services web (ce service interroge simplement la base de registres !).
Si vous venez d'installer Delphi 2006 (ou que vous n'avez jamais créé de Services Web), SERVERINFO.EXE n'est pas enregistré. Pour l'enregistrer la première fois:
| sélectionnez C:\Program Files\Borland\BDS\4.0\Bin et lancez SERVERINFO.EXE |
Notez que: - Cette opération n'aura plus besoin d'être effectuée
- à titre anectdotique, SERVERINFO figurait bien dans la liste des WAD de la base de registre (image avant la précédente)
3.3.2 - Lancement de notre Serveur Web Service Lançons à présent notre CGI:
Sur la dernière figure - trois fenêtre sont à l'écran:
- la fenêtre d'information sur les Services Web disponibles actuellement, soit 2 services
- la fenêtre de ServerInfo (A)
- la fenêtre de notre service en jaune (B)
- sur la fenêtre d'information, nous avons une partie pour chaque service (1 et 2).
- prenons notre service: en (3) il y a un hyper-lien qui permet de visualiser le fichier WSDL
3.3.3 - Consultation et Sauvegarde du .WSDL Pour visualiser le .WSDL: | cliquez sur le lien (3) |
| le texte du .WSDL est affiché dans une fenêtre Web:
Sans rentrer dans le détail des données .WSDL, vous pouvez reconnaître des information sur les méthodes et les paramètres de ce qui est offert par notre service |
Pour sauvegarder les données dans un fichier
| sélectionnez "Fichier | Enregistre Sous" et placez le fichier dans le même répertoire que les autres fichiers (le nom i_server_euro_dollar.xml est
proposé par défaut et nous le conservons) |
4 - Création d'un Client Web Service 4.1 - Consommation du Service
4.1.1 - Invocation d'une méthode Serveur Nous allons à présent créer un Client qui va appeler nos méthodes depuis une application connectée à notre Serveur par TCP/IP.
Nous pourrions construire manuellement et envoyer la requête SOAP au Serveur, puis récupérer la réponse et la décortiquer. Cette solution est possible en utilisant des Sockets TCP/IP. C'est en fait la solution que nous avions
adoptée pour notre conférence SOAP et DataSnap à la conférence Borland en 2001. Delphi utilise une technique qui automatise ces échanges sans que nous ayons à
traiter les messages SOAP. Au niveau principe:
Le résultat de cette mécanique est que le Client effectue ses appels de FUNCTION ou de PROCEDURE "comme si" la CLASSe Serveur avait été téléchargée sur son PC.
Le Proxy est mis en oeuvre en utilisant un composant tHttpRIO (RIO= Remote Invocation Object)
4.1.2 - Les composants Delphi
Le composant tHttpRIO a pour objectif de nous retourner une INTERFACE correspondant à l'INTERFACE sur le Serveur. Pour cela, il faut que le Client ait la définition Pascal de cette INTERFACE
Et pour importer la définition de l'INTERFACE, nous utilisons un Wizard qui: - lit un .WSDL (par son URL ou en lisant un fichier .XML)
- construit l'UNITé contenant:
- l'INTERFACE de notre Service (ainsi que les autres définitions si notre Service comporte des types ou CLASSes annexes particulières, ce qui n'est pas le cas pour notre exemple)
- en bonus, une FUNCTION qui retourne automatiquement cette INTERFACE
Il s'avère que nous ne pouvons invoquer le Wizard qu'après avoir créé une application Client. Nous allons donc
- créer une application Client
- créer à l'aide du Wizard l'UNITé qui contiendra l'INTERFACE
- compléter, dans la tForm de notre client, quelques appels de notre Service
4.2 - Création du Client Il s'agit d'une application Delphi des plus classiques: | sélectionnez "File | New | Vcl Application Form" |
| sauvegardez le projet. Nous suggérons de créer un répertoire CLIENT\ et de nommer les fichiers U_F_CLIENT_EURO_DOLLAR et P_CLIENT_EURO_DOLLAR |
| compilez pour vérifier que tout est en ordre |
Notez que - vous pourriez aussi ajouter la nouvelle application au groupe par "Project
Manager | ProjectGroup1 | Add New Project etc
4.3 - Importation de l'INTERFACE par le Wizard Maintenant nous pouvons utiliser le Wizard qui va transformer le .WSDL en une CLASSe locale du Client:
| sélectionnant "File | New | Other | Delphi Project | Web Services | Wsdl Importer"
et cliquez "Ok" | | le Wsdl Import Wizard est affiché (Ed: tronqué en largeur)
|
| dans "Location of WSDL File", tapez le chemin et le nom du fichier .XML. Dans notre cas: C:\programs\fr\web\web_service_tutorial\server_wad\i_server_euro_dollar.xml
(vous pouvez utiliser l'ellipse ... pour localiser ce fichier | | cliquez sur "Finish" |
| l'unité est présentée (Ed: tronqué en largeur): |
| cliquez "Finish" | |
l'unité est copiée dans notre projet Client, sous le nom de I_SERVER_EURO_DOLLAR1 | |
nous suggérons de renommer l'UNITé U_I_CLIENT_EURO_DOLLAR |
L'unité d'importation comporte (en enlevant les commentaires):
unit u_i_client_euro_dollar1; interface
uses InvokeRegistry, SOAPHTTPClient, Types, XSBuiltIns;
type i_server_euro_dollar =
interface(IInvokable)
'{091A6317-83B7-FB51-3FD6-4BD47A83325F}']
function f_dollar_to_euro(const p_dollar: Double): Double; stdcall;
procedure euro_to_dollar(const p_euro: Double;
var pv_dollar: Double); stdcall;
end; // i_server_euro_dollar
function Geti_server_euro_dollar(UseWSDL: Boolean=System.False;
Addr: string=''; HTTPRIO: THTTPRIO = nil): i_server_euro_dollar;
implementation
function Geti_server_euro_dollar(UseWSDL: Boolean;
Addr: string; HTTPRIO: THTTPRIO): i_server_euro_dollar;
const
defWSDL = 'C:\programs\fr\web\web_service_tutorial\server_wad\'
+ 'i_server_euro_dollar.xml';
defURL = 'http://localhost:8081/'
+ 'p_serveur_euro_dolar_wad.co_euro_dollar/soap/'
+ 'i_server_euro_dollar';
defSvc = 'i_server_euro_dollarservice';
defPrt = 'i_server_euro_dollarPort';
var RIO: THTTPRIO; begin
Result := nil;
if (Addr = '')
then begin
if UseWSDL
then Addr := defWSDL
else Addr := defURL;
end;
if HTTPRIO = nil
then RIO := THTTPRIO.Create(nil)
else RIO := HTTPRIO;
try
Result := (RIO as i_server_euro_dollar);
if UseWSDL
then begin
RIO.WSDLLocation := Addr;
RIO.Service := defSvc;
RIO.Port := defPrt;
end
else RIO.URL := Addr;
finally
if (Result = nil) and (HTTPRIO = nil)
then RIO.Free;
end; // try ... finally
end; // Geti_server_euro_dollar initialization
InvRegistry.RegisterInterface(TypeInfo(i_server_euro_dollar),
'urn:u_i_server_euro_dollar-i_server_euro_dollar', 'utf-8');
InvRegistry.RegisterDefaultSOAPAction(TypeInfo(i_server_euro_dollar),
'urn:u_i_server_euro_dollar-i_server_euro_dollar#%operationName%'); end.
| et: - nous retrouvons bien l'INTERFACE que nous avions côté Serveur
- la fonction Geti_server_euro_dollar permet simplement de nous retourner une
INTERFACE, avec plusieurs possibilités
- si nous ne fournissons aucun paramètres, un tHttpRio local est créé, et est retourné transtypé en i_server_euro_dollar
- si nous demandons un .WSDL, i_server_euro_dollar pourra être utilisé pour récupérer le .WSDL
- nous pouvons aussi placer un tHttpRio sur la tForm Client, et fournir
celui-ci comme paramètre après l'avoir initialisé
Comme nous appellerons Geti_server_euro_dollar sans paramètres, nous pouvons (c'est optionnel) simplifier cette fonction.
Voici les modifications (cosmétiques) que nous apportons: - changez le nom i_server_euro_dollar en i_client_euro_dollar (l'INTERFACE correspond à un proxy)
- supprimez les paramètres de Geti_server_euro_dollar
- veillez à ce que les chaînes littérales du bas appellent bien 'i_server_euro_dollar': ce sont elles qui seront utilisées pour appeler le Serveur
Voici notre unité modifiée:
unit u_i_client_euro_dollar; interface
uses InvokeRegistry, SOAPHTTPClient, Types, XSBuiltIns;
type i_client_euro_dollar =
interface(IInvokable)
['{091A6317-83B7-FB51-3FD6-4BD47A83325F}']
function f_dollar_to_euro(const p_dollar: Double): Double; stdcall;
procedure euro_to_dollar(const p_euro: Double;
var pv_dollar: Double); stdcall;
end; // i_client_euro_dollar
function f_i_client_euro_dollar: i_client_euro_dollar;
implementation
function f_i_client_euro_dollar: i_client_euro_dollar;
const k_service_url= 'http://localhost:8081/'
+ 'p_serveur_euro_dolar_wad.co_euro_dollar/soap/'
+ 'i_server_euro_dollar';
var l_c_http_remote_invokation_object: THTTPRIO;
begin
l_c_http_remote_invokation_object:= THTTPRIO.Create(nil);
Try
Result := (l_c_http_remote_invokation_object as i_client_euro_dollar);
l_c_http_remote_invokation_object.URL:= k_service_url
finally
if Result = nil
then l_c_http_remote_invokation_object.Free;
end; // try ... finally
end; // f_i_client_euro_dollar initialization
InvRegistry.RegisterInterface(TypeInfo(i_client_euro_dollar),
'urn:u_i_server_euro_dollar-i_server_euro_dollar', 'utf-8');
InvRegistry.RegisterDefaultSOAPAction(TypeInfo(i_client_euro_dollar),
'urn:u_i_server_euro_dollar-i_server_euro_dollar#%operationName%'); end. |
Notez que - les deux appels de InvRegistry correspondent aussi à des enregistrement "locaux" (pas dans la Registry Windows) et permettent de transformer nos
appels de i_client_euro_dollar en des requêtes HTTP au format SOAP
- le GUID de l'INTERFACE importée n'a rien à voir avec le GUI sur le Serveur. Ici aussi ce GUID sert uniquement à identifier de façon unique
cette INTERFACE dans la liste de correspondance locale au Client
4.4 - Utilisation de notre Proxy Finissons par le programme Client. Nous allons simplement effectuer une
conversion d'une valeur contenue dans un tEdit, et la conversion sera effectuée lorsque nous cliquons sur un tButton: |
sélectionnez la tForm principale | | placez un tEdit, un tLabel et un tButton sur Form1 |
| créez l'événement Button1Click et tapez le code qui convertira un montant (nous avons utilisé deux locales auxiliaires qui auraient pu être évitées):
procedure TForm1.dollar_to_euro_Click(Sender: TObject);
var l_euro_amount, l_dollar_amount: Double;
begin
l_dollar_amount:= StrToFloat(dollar_edit_.Text);
// -- appel du service l_euro_amount:=
f_i_client_euro_dollar().f_dollar_to_euro(l_dollar_amount);
euro_label_.Caption:= FloatToStr(l_euro_amount);
end; // dollar_to_euro_Click | | |
importez aussi U_I_CLIENT_EURO_DOLLAR dans la Forme principale | | compilez, exécutez, tapez un montant en dollar, cliquez sur le bouton |
| voici le résultat: |
Notez que
- nous avons pu exécuter directement notre Client, car notre Serveur était encore chargé (même si le projet Serveur a été fermé, le CGI reste lancé par le Web App Debbugger)
Le Web App Debugger stocke les messages échangés entre le Client et le Serveur:
5 - Améliorations et Commentaire 5.1 - Compliqué ? Il est à craindre que la lecture de ce document ne fasse apparaître les
Services Web comme horriblement compliqués. Pourtant Delphi a fait des efforts considérables pour limiter les manipulations au plus strict nécessaire. En réalité, le fait que nous ayons passé du temps à expliquer certains
fonctionnement a considérablement alourdi la présentation. Si vous créez deux ou trois services - les manipulations initiales (comme le premier lancement de ServerInfo, par exemple) n'auront pas à être répétées
- l'utilisation d'un groupe de projet simplifie énormément les aller-retour lorsque nous modifions l'INTERFACE du Service
- les différentes manipulations deviendront automatiques
Nous avons même simplifié le traitement en créant un programme de transformation qui fonctionne ainsi: - nous avons créer un Service Web (Serveur et Client), en nommant le
"service" (l'équivalent de "euro_dollar") "xxx"
- notre utilitaire copie les fichiers de cette application simple vers un autre répertoire en renommant "xxx" par le nom que nous avons fourni
Dans ce cas, il ne reste plus qu'à
- au niveau Serveur, entrer les méthodes du Service, et les implémenter
- au niveau Client:
- copier les méthodes dans le client
- taper le code utilisateur du Client
5.2 - Conventions de noms Nous avons utilisé des convention de préfixes forts (i_xxx, u_i_xxx, c_xxx ainsi que le mot "serveur" pour le Serveur et "client" pour le Client, et les
suffixes "_wad", "_cgi" et "_isapi" pour distinguer les différents types d'extension Serveur). Comme d'habitude, vous n'êtes pas obligés de suivre ces conventions. En particulier, le fait de supprimer "serveur" et "client" dans le nom de
l'INTERFACE (par exemple iEuroDollar au lien de i_serveur_euro_dollar et i_client_euro_dollar) permet quasiment d'utiliser la même INTERFACE pour le Serveur et le Client
Mais nous restons, quant à nous, fidèles aux préfixes. Dans ce cas, ils nous ont permis de bien comprendre les relations entre les différentes pièces qui entrent en jeu. Si tous les identificateurs sont identiques, il est aisé de
croire qu'il s'agit d'un objet alors qu'il s'agit d'un autre. Et en utilisant l'utilitaire de transformation, les manipulation concernant les préfixes sont quasiment inexistantes.
5.3 - Ce qui manque
Cet exemple simple avait pour unique but de montrer comment construire un service web. Nous aurions pu ajouter - le traitement de types complexes (tableaux, CLASSes, tDataSets,
- le transfert de données (fichiers) binaires
- les pièces attachées (SOAP Attachments)
- les transferts de données au format .XML
- les échanges avec Datasnap, en utilisant des tSoapConnection
- la création d'extensions CGI et ISAPI
- le déploiement sur des Serveurs HTTP (IIS, Apache)
- le débugging du Serveur Web Service en utilisant le Web App Debugger
- les conversions entre des applications CGI, ISAPI et Web App Debugger
- les diagrammes de Classe UML présentant les différents composants Delphi (tWebApp, tHttpRio ...) mis en oeuvre
5.4 - Formation Web Service Les points mentionnés ci-dessus sont abordés lors de nos
Formations XML Web Services. Nous avons
regroupé la présentation .XML avec la présentation Services Web, car une connaissance du format .XML est utile, et il arrive que les applications Service Web aient à traiter des données au format .XML, indépendemment de SOAP.
5.5 - Et .Net dans tout cela ? Notre présentation a été effectuée en mode Win32. En fait les Services Web correspondent à un format, lequel peut être implémenté et utilisé sur la
plupart des ordinateur (Mac, PC), sous divers Operating Systems (Windows, Linux, Unix, MacOs). Par conséquent ils peuvent aussi être utilisés (Serveur et Client) dans
l'environnement .NET. Ce qui change ce sont les composants et les manipulations de mise en oeuvre. Celles-ci sont d'ailleurs présentées lors de nos formations
Delphi 2006 .Net ainsi que pendant les
formations ASP.NET Delphi et
ASP.NET 2.0 Delphi
5.6 - Next Au niveau explications, nous aurions aussi pu détailler
- le fonctionnement de WebBroker
- la mécanique de iInvoke
Il existe une étape au-delà des Services Web : les objets distribués. Pour expliquer la hiérarchie, nous avons
- les extensions CGI, qui nécessitent un explorateur Web, l'appel d'une page contenant une balise <FORM
- les Services Web, que l'utilisateur peut invoquer depuis n'importe quelle
application exécutable (pas besoin d'explorateur internet)
Ces services sont limités à des appels de FUNCTIONs et de PROCEDUREs. Les traitement réalisés peuvent être très compliqué (modifier une base de
donnée, retourner la carte de votre domicile), mais comme il s'agit d'INTERFACE, la mécanique de base ne gère pas d'état. Une fois la réponse retournée, le Serveur ne se souvient plus de vous. Si votre traitement
nécessite plusieurs étapes (lecture paginée de Tables), c'est à vous de gérer ces sessions au niveau Client - les objets distribués: comme les objets contiennent des données, ils sont plus puissants qu'une simple collection de méthodes
Nous présentons les objets distribuées lors de nos formations .Net
6 - Télécharger le code source Delphi Vous pouvez télécharger: Ce .ZIP qui comprend: - les fichiers .DPR, PAS, DFM
- le fichier .WSDL
Pour l'utiliser: - dézippez, sélectionnez le groupe, compilez
Ce .ZIP ne modifie pas votre PC (pas de changement de la Base de Registre, de DLL ou autre). Pour supprimer le projet, effacez le répertoire.
Comme d'habitude:
- nous vous remercions de nous signaler toute erreur, inexactitude ou problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections
qui en résulteront pourront aider les prochains lecteurs
- tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même
les bienvenus à jcolibri@jcolibri.com.
- plus simplement, vous pouvez taper (anonymement ou en fournissant votre e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en
cliquant "envoyer" :
- et si vous avez apprécié cet article, faites connaître notre site,
ajoutez un lien dans vos listes de liens ou citez-nous dans vos blogs ou réponses sur les messageries. C'est très simple: plus nous aurons de visiteurs et de références Google, plus nous écrirons d'articles.
7 - Références Quelques références concernant les Services Web: - Web Services in Delphi 2007 par Bruneau
BABET de CodeGear - conférence en ligne CodeRage I - Mars 2007 - Video .SWF de 39 Meg.
Mentionnons aussi que Bruneau BABET est très présent au niveau des newsgroup
Delphi (borland.public.delphi.webservices.soap), et répond bien volontiers aux questions qui sont posées. - au niveau de nos articles:
- présentation SOAP et DataSnap : les transparent de notre présentation à la conférence Borland en 2001.
Pour des Serveurs HTTP capables de traiter SOAP: - pour le Serveur
CGI en source à partir des Sockets Delphi. Ce serveur capable de gérer des requêtes CGI ne permet pas de traiter des messages SOAP en l'état
- il a été modifié pour permettre SOAP, mais cette version n'a pas été publiée
à ce jour
- il existe plusieurs serveurs à base d'Indy, démarrés par Dave NOTTAGE, et amélioré par la suite. Google ou Code Central vous permettront de les récupérer
Les points mentionnés ci-dessus sont abordés lors de nos différentes formations:
8 - L'auteur
John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le
conseil (composants, architecture, test) et la
formation. Son site contient des articles
avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et
UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client. |